﻿using System;
using System.Collections.Generic;
using System.Linq;
using Engine;
using Engine.AI.Astar;
using Engine.Math;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ThinkDotNetBotApiCommon;
using ThinkDotNetBotApiCommon.Weapons;
using ThinkDotNetEngine.Bots.Ammos;
using ThinkDotNetEngine.Bots.Weapons;

namespace ThinkDotNetEngine.Bots
{
    public class TankBot:IBoxableAstarStateAware
    {        
        #region Consts\Readonly

        public const float Speed = 0.5f;
        public const float MaxHealth=100f;
        private const string CannonBoneName = "canon_geo";
        private const float RotationSpeed = 0.01f;
        private const float TranslationOffsetEpsilon = 1;
        private const float RotationOffsetEpsilon = 0.1f;

        private readonly Arena Arena;
        private readonly AStarComponent _astar;

        #endregion     
   
        #region Fields

        private Vector3 _firePos;                

        #endregion

        public TankBot(BotBase bot,Arena arena,Vector3 startPos)
        {
            Arena = arena;
            DestinationPoint = Translation = startPos;
            AllWeapons=new Dictionary<CannonWeaponType, IWeaponBase>();            
            Bot = bot;
            _astar = new AStarComponent();
        }

        #region Properties

        public long Id { get { return GetHashCode(); } }
        public ModelBone CannonBone { get; private set; }
        public BoundingBox InitialBoundingBox { get; private set; }
        public BotBase Bot { get; private set; }
        public float DestinationRotationY { get; private set; }
        private float _rotationY;
        public float RotationY
        {
            get { return _rotationY; }
            private set { _rotationY = value; }
        }
        private float _cannonRotation;
        public float CannonRotation
        {
            get { return _cannonRotation; }
            private set { _cannonRotation = value; }
        }
        public float DestinationCannonRotation { get; private set; }
        private float _turretRotation;
        public float TurretRotation
        {
            get { return _turretRotation; }
            private set { _turretRotation = value; }
        }

        public Matrix[] BoneAbsolutes { get; set; }
        public float _currentHealth = MaxHealth;
        public float CurrentHealth { get { return _currentHealth; } private set { _currentHealth = value; } }
        public float DestinationTurretRotation { get; private set; }
        public Vector3 Translation { get;  set; }
        public Vector3 DestinationPoint { get; private set; }
        private Matrix _worldMatrix = Matrix.Identity;
        public Matrix WorldMatrix { get { return _worldMatrix; } }
        public event EventHandler Destroyed;

        public IWeaponBase CurrentWeapon { get; private set; }
        public IDictionary<CannonWeaponType, IWeaponBase> AllWeapons { get; private set; }
        public WeaponBase TacticalMissileWeapon { get; set; }

        public TankClip CurrentClip { get; private set; }

        public Model Model { get; private set; }

        private Matrix[] _bones;

        public bool IsMoving
        {
            get
            {
                if(Translation==DestinationPoint&&AStarComponent.Path.Count==0)
                    return false;
                return true;
            }
        }
        public bool IsTurretReady
        {
            get { return Math.Abs(TurretRotation - DestinationTurretRotation) < RotationOffsetEpsilon; }
        }
        public bool IsCannonReady
        {
            get { return Math.Abs(CannonRotation - DestinationCannonRotation) < RotationOffsetEpsilon; }
        }
   
        public AStarComponent AStarComponent { get { return _astar; } }

        #endregion

        #region Private Methods

        private void UpdateRotation()
        {
            CurrentClip = TankClip.Idle;
            UpdateSmoothlyRotationValue(ref _rotationY, DestinationRotationY);
        }
        private void UpdateCurrentPosition()
        {
            if (!AStarComponent.IsPathComputed || AStarComponent.Path.Count == 0) return;

            CurrentClip = TankClip.Move;

            BoxableItemAstarState state = (BoxableItemAstarState)AStarComponent.Path[0];
            DestinationPoint = state.Position;

            if (Vector3.Distance(Translation, DestinationPoint) < TranslationOffsetEpsilon)
            {
                Translation = DestinationPoint;
                AStarComponent.Path.RemoveAt(0);
                if (AStarComponent.Path.Count == 0)
                    CurrentClip = TankClip.Idle;
            }
            else if (DestinationPoint != Translation)
            {
                Vector3 direction = DestinationPoint - Translation;
                direction.Normalize();
                Vector3 offset=direction * Speed;
                
                if(Arena.DetectCollisionWithOtherBotsAfterMovement(this,offset).Any())
                {
                    Stop();
                    return;
                }
                Translation += offset;
                if (SetDirection(direction))
                {
                    float newHeight = Arena.Terrain.GetHeight(Translation);
                    Translation = new Vector3(Translation.X, newHeight, Translation.Z);
                }
            }
        }
        private Matrix UpdateOrientation()
        {
            Matrix orientation = Matrix.CreateRotationY(RotationY);
            Vector3 normal;

            Arena.Terrain.GetNormal(Translation, out normal);
            orientation.Up = normal;

            orientation.Right = Vector3.Cross(orientation.Forward, orientation.Up);
            orientation.Right = Vector3.Normalize(orientation.Right);

            orientation.Forward = Vector3.Cross(orientation.Up, orientation.Right);
            orientation.Forward = Vector3.Normalize(orientation.Forward);

            return orientation;
        }
        private void UpdateSmoothlyRotationValue(ref float rotationValue, float destinationRotationValue)
        {
            if (MathHelperEx.AreAngleEqual(rotationValue , destinationRotationValue, RotationOffsetEpsilon))
            {
                rotationValue = destinationRotationValue;
                return;
            }
            float angle = Math.Abs(destinationRotationValue - rotationValue)%MathHelper.TwoPi;
            float offset = RotationSpeed;

            if (destinationRotationValue < rotationValue)
                offset = -RotationSpeed;

            if (angle > MathHelper.Pi)
                offset = -offset;
            
            rotationValue = (rotationValue + offset)%MathHelper.TwoPi;
        }
        private void UpdateTurretAndCannon()
        {
            UpdateSmoothlyRotationValue(ref _turretRotation, DestinationTurretRotation);
            UpdateSmoothlyRotationValue(ref _cannonRotation, DestinationCannonRotation);
        }
        private Matrix GetFirePosTransform()
        {
            return BoneAbsolutes[CannonBone.Index] * WorldMatrix;
        }
        private void Destroy()
        {
            CurrentHealth = 0;
            OnDestroyed();
        }

        #endregion
        
        #region  Protected Methods

        virtual protected void OnDestroyed()
        {
            if (Destroyed != null)
                Destroyed(this, EventArgs.Empty);
        }
        
        #endregion

        #region Public Methods

        public void Load(Game game,string assetName)
        {
            Model = game.Content.Load<Model>(assetName);                       
            _bones=new Matrix[Model.Bones.Count];
            Model.CopyAbsoluteBoneTransformsTo(_bones);

            CannonBone = Model.Bones[CannonBoneName];
            var tagDictionary = (Dictionary<string, object>) Model.Tag;
            InitialBoundingBox = (BoundingBox)tagDictionary["BoundingBox"];
            var subMeshBoundingBoxes = (Dictionary<string,BoundingBox>)tagDictionary["SubMeshBoundingBoxes"];
            BoundingBox canonBoundingBox = subMeshBoundingBoxes[CannonBone.Name];

            _firePos = (canonBoundingBox.Min + canonBoundingBox.Max) / 2f;
            _firePos = new Vector3(_firePos.X, _firePos.Y, canonBoundingBox.Max.Z);
        }
        public IScanableItem[]  ScanObjects(float radius)
        {
            return Arena.GetAllObjectsInRadiusArea(Translation, radius).Where(o=>o.Id!=Id).ToArray();
        }
        public bool SetDirection(Vector3 direction)
        {
            float angle = (float) MathHelperEx.GetAngleBetweenVectors(new Vector2(direction.X, direction.Z),
                                                                      Vector2.UnitY);
            DestinationRotationY = -angle + MathHelper.TwoPi;

            if (MathHelperEx.AreAngleEqual(RotationY, DestinationRotationY, RotationOffsetEpsilon))
                return true;

            return false;
        }
        public void MoveTurret(float value)
        {
            DestinationTurretRotation = value;
        }
        public void MoveCannon(float value)
        {
            DestinationCannonRotation = MathHelper.Clamp(value, -MathHelper.PiOver4, MathHelper.PiOver4);
        }
        public void Stop()
        {
            CurrentClip = TankClip.Idle;
            DestinationPoint = Translation;
            AStarComponent.Cancel();
        }
        public void Move(Vector3 destinationPoint)
        {
            BoxableItemAstarState goalState = new BoxableItemAstarState(this, Arena, destinationPoint, null, null);
            BoxableItemAstarState startState = new BoxableItemAstarState(this, Arena, Translation, null, goalState);
            AStarComponent.Traverse(startState, goalState);
            DestinationPoint = destinationPoint;
        }
        public void Update(GameTime gameTime)
        {
            AStarComponent.Update();

            if (AStarComponent.IsPathComputed == false)
                return;
            
            UpdateTurretAndCannon();
            if (!MathHelperEx.AreAngleEqual(RotationY, DestinationRotationY, RotationOffsetEpsilon))
                UpdateRotation();
            else
                UpdateCurrentPosition();

            Matrix orientation = UpdateOrientation();

            _worldMatrix = orientation*Matrix.CreateTranslation(Translation);

            try
            {
                Bot.Update(gameTime);
            }
            catch (Exception)
            {
                Destroy();
            }            
        }
        public void LaunchTacticalMissile(Vector3 destination)
        {
            Vector3 source = Arena.SkyCenterTop;
            Vector3 direction = destination - source;
            direction.Normalize();
            TacticalMissileWeapon.Launch(direction, source);
        }
        public bool LaunchAmmo()
        {
            if (CurrentWeapon == null)
                return false;

            ((WeaponBase)CurrentWeapon).Launch(GetCannonForward(), GetCannonPos());

            return true;
        }
        public Vector3 GetCannonPos()
        {
            Matrix firePosTransformMatrix = GetFirePosTransform();
            return  Vector3.Transform(_firePos, firePosTransformMatrix);            
        }
        public Vector3 GetCannonForward()
        {
            return -(BoneAbsolutes[CannonBone.Index]*WorldMatrix).Forward;
        }
        public bool ChangeWeapon(CannonWeaponType weaponType)
        {
            if (!AllWeapons.ContainsKey(weaponType))
                return false;
            CurrentWeapon = AllWeapons[weaponType];
            return true;
        }
        public void Hit(AmmoBase ammoBase)
        {
            _currentHealth -= ammoBase.HitPower;
            if(_currentHealth<=0)
            {
                Destroy();
            }
        }
    
        #endregion
   
    }
}
